/*
 * Copyright 2008 Wink Saville
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
import java.io.*;
import junit.framework.Assert;
import java.util.Vector;

import com.saville.debug.*;
import com.saville.serdes.*;

/*
 * Test Serialization/Deserialization
 */
public class TestSerdes {

    /** Called when the activity is first created. */
    public static void main(String[] args) {
        Log.appName = "TestSerdes";

        new SerdesBinary();
        new SerdesJson();
        new StringSer();

        testJsonSerialization();
        testBinarySerialization();
        testPerformance();
    }

    static void testJsonSerialization() {
        try {
            SerdesJson serdesJson = new SerdesJson();
            String tStrg = "12345678901234567890123456789012";
            byte jsonData[];
            String strg;
            StringSer ss;

            /* Test String serialization */
            ss = new StringSer(tStrg);
            serdesJson.init();
            ss.serialize(serdesJson);
            jsonData = serdesJson.data();
            serdesJson.init(jsonData);
            strg = (String) serdesJson.readObject();
            Assert.assertEquals(strg, tStrg);
            
            /* Test empty  VectorStringSer */
            VectorStringSer vss = new VectorStringSer();
            serdesJson.init();
            vss.serialize(serdesJson);
            jsonData = serdesJson.data();
            serdesJson.init(jsonData);
            VectorStringSer vssr = (VectorStringSer)serdesJson.readObject();
            Assert.assertEquals(0, vssr.strgs.size());
            
            /* Test Vector<String> serialization */
            vss = new VectorStringSer();
            vss.strgs.add("abc");
            vss.strgs.add("def");
            vss.strgs.add("ghi");
            serdesJson.init();
            vss.serialize(serdesJson);
            jsonData = serdesJson.data();
            serdesJson.init(jsonData);
            vssr = (VectorStringSer)serdesJson.readObject();
            Assert.assertEquals("abc", vssr.strgs.get(0));
            Assert.assertEquals("def", vssr.strgs.get(1));
            Assert.assertEquals("ghi", vssr.strgs.get(2));

        } catch (Exception e) {
            Log.print("Exception e=%s", e);
            Log.printStackTrace(e);
        }
    }

    static void testBinarySerialization() {

        try {
            SerdesBinary serdesBinary = new SerdesBinary();
            byte b[];

            String tStrg = "12345678901234567890123456789012";
            byte data[] = new byte[32];

            for (int i = 0; i < data.length; i++) {
                data[i] = (byte) (i & 0xff);
            }

            serdesBinary.init();
            serdesBinary.writeBoolean(true);
            serdesBinary.writeBoolean(false);
            serdesBinary.writeByte(-1);
            serdesBinary.writeByte(0xff);
            serdesBinary.writeByte(0x0);
            serdesBinary.writeShort(-1);
            serdesBinary.writeShort(0xffff);
            serdesBinary.writeShort(0x7f7f);
            serdesBinary.writeShort(0x0);
            serdesBinary.writeInt(0xffffffff);
            serdesBinary.writeInt(0x7f7f7f7f);
            serdesBinary.writeInt(0x0);
            serdesBinary.writeLong(0xffffffffffffffffL);
            serdesBinary.writeLong(0x7f7f7f7f7f7f7f7fL);
            serdesBinary.writeLong(0x0);
            b = serdesBinary.data();
            serdesBinary.init(b, 0, serdesBinary.size());
            Assert.assertEquals(true, serdesBinary.readBoolean());
            Assert.assertEquals(false, serdesBinary.readBoolean());
            Assert.assertEquals(-1, serdesBinary.readByte());
            Assert.assertEquals(0xff, serdesBinary.readUnsignedByte());
            Assert.assertEquals(0x0, serdesBinary.readByte());
            Assert.assertEquals(-1, serdesBinary.readShort());
            Assert.assertEquals(0xffff, serdesBinary.readUnsignedShort());
            Assert.assertEquals(0x7f7f, serdesBinary.readShort());
            Assert.assertEquals(0x0, serdesBinary.readShort());
            Assert.assertEquals(0xffffffff, serdesBinary.readInt());
            Assert.assertEquals(0x7f7f7f7f, serdesBinary.readInt());
            Assert.assertEquals(0x0, serdesBinary.readInt());
            Assert.assertEquals(0xffffffffffffffffL, serdesBinary.readLong());
            Assert.assertEquals(0x7f7f7f7f7f7f7f7fL, serdesBinary.readLong());
            Assert.assertEquals(0x0, serdesBinary.readLong());

            serdesBinary.init();
            serdesBinary.writeBytes(data);
            serdesBinary.init(serdesBinary.data(), 0, serdesBinary.size());
            b = serdesBinary.readBytes();
            for (int i = 0; i < b.length; i++) {
                if (b[i] != data[i]) {
                    throw new RuntimeException("serdes.writeBytes/readBytes error");
                }
            }

            /* Test String serialization */
            StringSer ss = new StringSer("hi");
            serdesBinary.init();
            ss.serialize(serdesBinary);
            b = serdesBinary.data();
            serdesBinary.init(b, 0, serdesBinary.size());
            String strg = (String) serdesBinary.readObject();
            Assert.assertEquals(strg, "hi");
            
            /* Test empty */
            VectorStringSer vss = new VectorStringSer();
            serdesBinary.init();
            vss.serialize(serdesBinary);
            b = serdesBinary.data();
            serdesBinary.init(b, 0, serdesBinary.size());
            VectorStringSer vssr = (VectorStringSer)serdesBinary.readObject();
            Assert.assertEquals(0, vssr.strgs.size());
            
            /* Test Vector<String> serialization */
            vss = new VectorStringSer();
            vss.strgs.add("abc");
            vss.strgs.add("def");
            vss.strgs.add("ghi");
            serdesBinary.init();
            vss.serialize(serdesBinary);
            b = serdesBinary.data();
            serdesBinary.init(b, 0, serdesBinary.size());
            vssr = (VectorStringSer)serdesBinary.readObject();
            Assert.assertEquals("abc", vssr.strgs.get(0));
            Assert.assertEquals("def", vssr.strgs.get(1));
            Assert.assertEquals("ghi", vssr.strgs.get(2));
            
            /* Test Vector<String> serialization */
            Vector<String> vs = new Vector<String>();
            vs.add("abc");
            vs.add("def");
            vs.add("ghi");
            vss = new VectorStringSer(vs);
            serdesBinary.init();
            vss.serialize(serdesBinary);
            b = serdesBinary.data();
            serdesBinary.init(b, 0, serdesBinary.size());
            vssr = (VectorStringSer)serdesBinary.readObject();
            Assert.assertEquals("abc", vssr.strgs.get(0));
            Assert.assertEquals("def", vssr.strgs.get(1));
            Assert.assertEquals("ghi", vssr.strgs.get(2));


            /* TODO: Add more tests */

            /* Print lengths */
            int l = tString(tStrg);
            Log.print("<String std> l=%d", l);
            l = tStringSer(tStrg);
            Log.print("<StringSer> l=%d", l);
            l = tStringSerJson(tStrg);
            Log.print("<StringSer json> l=%d", l);
        } catch (Exception e) {
            Log.print("Exception e=%s", e);
            Log.printStackTrace(e);
        }
    }

    public static void testPerformance() {

        try {
            SerdesBinary serdesBinary = new SerdesBinary();
            SerdesJson serdesJson = new SerdesJson();
            byte b[];

            String tStrg = "12345678901234567890123456789012";
            byte data[] = new byte[32];

            for (int i = 0; i < data.length; i++) {
                data[i] = (byte) (i & 0xff);
            }

            /* Variables for performance measurements */
            int count = 10;
            long durs[] = new long[count];
            int len;

            /*
             * Measure performance of standard serialization for an array of
             * bytes
             * 
             * I've observed the first time is quite slow so as not to overly
             * skew the results to one first.
             */
            long st = Timing.readTsc();
            tArray(data);
            double ds = Timing.tscToSecs(Timing.readTsc() - st);
            Log.print("First ds=%f rate=%f/s time/per=%f", ds, 1.0 / ds, ds);
            st = Timing.readTsc();
            len = tArray(data);
            ds = Timing.tscToSecs(Timing.readTsc() - st);
            Log.print("data len=%d ds=%f rate=%f/s time/per=%f", len, ds, 1.0 / ds, ds);

            st = Timing.readTsc();
            for (int k = 0; k < count; k++) {
                long start = Timing.readTsc();
                tArray(data);
                durs[k] = Timing.readTsc() - start;
            }
            ds = Timing.tscToSecs(Timing.readTsc() - st);
            double rate = count / ds;
            Log.print("AVG <byte std> ds=%f rate=%f/s time/per=%f", ds, rate, 1.0 / rate);
            for (long dur : durs) {
                ds = Timing.tscToSecs(dur);
                Log.print("         ds=%f rate=%f/s time/per=%f", ds, 1.0 / ds, ds);
            }

            /*
             * Measure performance of serializing a String
             */
            System.gc();
            st = Timing.readTsc();
            for (int k = 0; k < count; k++) {
                long start = Timing.readTsc();
                tString(tStrg);
                durs[k] = Timing.readTsc() - start;
            }
            ds = Timing.tscToSecs(Timing.readTsc() - st);
            rate = count / ds;
            Log.print("AVG <String std> ds=%f rate=%f/s time/per=%f", ds, rate, 1.0 / rate);
            for (long dur : durs) {
                ds = Timing.tscToSecs(dur);
                Log.print("         ds=%f rate=%f/s time/per=%f", ds, 1.0 / ds, ds);
            }

            /*
             * Measure performance of serializing a StringSer
             */
            System.gc();
            st = Timing.readTsc();
            for (int k = 0; k < count; k++) {
                long start = Timing.readTsc();
                tStringSer(tStrg);
                durs[k] = Timing.readTsc() - start;
            }
            ds = Timing.tscToSecs(Timing.readTsc() - st);
            rate = count / ds;
            Log.print("AVG <StringSer> ds=%f rate=%f/s time/per=%f", ds, rate, 1.0 / rate);
            for (long dur : durs) {
                ds = Timing.tscToSecs(dur);
                Log.print("         ds=%f rate=%f/s time/per=%f", ds, 1.0 / ds, ds);
            }

            /*
             * Measure performance of serializing a StringSer
             */
            System.gc();
            st = Timing.readTsc();
            StringSer tStrgSer = new StringSer(tStrg);
            for (int k = 0; k < count; k++) {
                long start = Timing.readTsc();
                tStringSer2(serdesBinary, tStrgSer);
                durs[k] = Timing.readTsc() - start;
            }
            ds = Timing.tscToSecs(Timing.readTsc() - st);
            rate = count / ds;
            Log.print("AVG <StringSer> ds=%f rate=%f/s time/per=%f", ds, rate, 1.0 / rate);
            for (long dur : durs) {
                ds = Timing.tscToSecs(dur);
                Log.print("         ds=%f rate=%f/s time/per=%f", ds, 1.0 / ds, ds);
            }

            /*
             * Measure performance of serializing a StringSer using json
             */
            System.gc();
            st = Timing.readTsc();
            tStrgSer = new StringSer(tStrg);
            for (int k = 0; k < count; k++) {
                long start = Timing.readTsc();
                tStringSerJson(serdesJson, tStrgSer);
                durs[k] = Timing.readTsc() - start;
            }
            ds = Timing.tscToSecs(Timing.readTsc() - st);
            rate = count / ds;
            Log.print("AVG <StringSer Json> ds=%f rate=%f/s time/per=%f", ds, rate, 1.0 / rate);
            for (long dur : durs) {
                ds = Timing.tscToSecs(dur);
                Log.print("         ds=%f rate=%f/s time/per=%f", ds, 1.0 / ds, ds);
            }

            System.gc();
            Log.print("done test");
        } catch (Exception e) {
            Log.print("Exception e=%s", e);
            Log.printStackTrace(e);
        }
    }

    static int tArray(byte data[]) throws Exception {
        ByteArrayOutputStream omb = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(omb);

        // Serialize
        oos.writeObject(data);
        oos.close();
        byte idata[] = omb.toByteArray();

        // Deserialize
        ByteArrayInputStream imb = new ByteArrayInputStream(idata);
        ObjectInputStream ois = new ObjectInputStream(imb);
        ois.readObject();

        return idata.length;
    }

    static int tString(String str) throws Exception {
        ByteArrayOutputStream omb = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(omb);

        // Serialize
        oos.writeObject(str);
        oos.close();
        byte idata[] = omb.toByteArray();

        // Deserialize
        ByteArrayInputStream imb = new ByteArrayInputStream(idata);
        ObjectInputStream ois = new ObjectInputStream(imb);
        ois.readObject();

        return idata.length;
    }

    static int tStringSer(String str) throws Exception {
        SerdesBinary serdesBinary = new SerdesBinary(str.length());

        // Serialize
        StringSer ss = new StringSer(str);
        ss.serialize(serdesBinary);
        byte b[] = serdesBinary.data();
        int len = serdesBinary.size();

        // Deserialize
        serdesBinary.init(b, 0, serdesBinary.size());
        serdesBinary.readObject();

        return len;
    }

    static int tStringSer2(SerdesBinary serdesBinary, StringSer str) throws Exception {
        // Serialize
        serdesBinary.init();
        str.serialize(serdesBinary);
        byte b[] = serdesBinary.data();
        int len = serdesBinary.size();

        // Deserialize
        serdesBinary.init(b, 0, serdesBinary.size());
        serdesBinary.readObject();

        return len;
    }

    static int tStringSerJson(String str) throws Exception {
        SerdesJson serdesJson = new SerdesJson();

        // Serialize
        StringSer ss = new StringSer(str);
        ss.serialize(serdesJson);
        byte b[] = serdesJson.data();
        int len = b.length;

        // Deserialize
        serdesJson.init(b);
        serdesJson.readObject();

        return len;
    }

    static int tStringSerJson(SerdesJson serdesJson, StringSer str) throws Exception {
        // Serialize
        serdesJson.init();
        str.serialize(serdesJson);
        byte b[] = serdesJson.data();
        int len = b.length;

        // Deserialize
        serdesJson.init(b);
        serdesJson.readObject();

        return len;
    }

}
